package io.gitee.loulan_yxq.owner.poi.excel.tool;

import io.gitee.loulan_yxq.owner.core.collection.ArrayTool;
import io.gitee.loulan_yxq.owner.core.collection.CollTool;
import io.gitee.loulan_yxq.owner.core.exception.IORuntimeException;
import io.gitee.loulan_yxq.owner.core.map.MapTool;
import io.gitee.loulan_yxq.owner.core.tool.AssertTool;
import io.gitee.loulan_yxq.owner.core.tool.ObjectTool;
import io.gitee.loulan_yxq.owner.core.tool.ReflectTool;
import io.gitee.loulan_yxq.owner.core.tool.StrTool;
import io.gitee.loulan_yxq.owner.poi.excel.entity.ExcelExportConfiguration;
import io.gitee.loulan_yxq.owner.poi.excel.entity.ExcelHeaderConfiguration;
import io.gitee.loulan_yxq.owner.poi.excel.exception.ExcelException;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/*********************************************************
 ** 写excel的类(*.xlsx)
 ** <br><br>
 ** Date: Created in 2022/2/10  20:07
 ** @author loulan
 ** @version 0.0.0
 *********************************************************/
public class ExcelXWriter implements ExcelWriter {
    // 工作表
    private final XSSFWorkbook workbook;

    // 最大行
    private final static Integer MAX_ROW = 1048576;
    // 最大列
    private final static Integer MAX_COL = 16384;

    /**
     * 写excel表到指定的输出流
     *
     * @param os        输出流
     * @param data      要写数据的list集合
     * @param sheetName 工作簿得名称
     * @author :loulan
     */
    @Override
    public void write(OutputStream os, String sheetName, List<?> data) {
        write(os, null, sheetName, data);
    }

    /**
     * 写数据到指定的输出流
     *
     * @param os          输出流
     * @param data        要写数据的list集合（可以是map集合，也可以是属性映射对象）
     * @param headerAlias 表头对应的别名，这个是对应list数据中对象的属性对应得名称，比如指定一个map数据为，key='uername',value='用户名'
     * @param sheetName   工作簿得名称
     * @author :loulan
     */
    @Override
    public void write(OutputStream os, Map<String, String> headerAlias, String sheetName, List<?> data) {
        // 1. 先进行数据校验判断
        // 先判断excel里面是否能够放下这些数据
        isGtMaxrow(CollTool.isNotEmpty(data) ? data.size() : 0);
        // 判断最大列是否能够放下表头数据
        isGtMaxcol(MapTool.isNotEmpty(headerAlias) ? headerAlias.size() : 0);

        // 2. 创建工作表
        // 通过工作簿创建工作表(如果工作表的名称为空的，就创建一个空表)
        XSSFSheet sheet = StrTool.isBlank(sheetName) ? workbook.createSheet() : workbook.createSheet(sheetName);

        // 3. 向工作表中写数据
        // 判断表头对应的别名是否存在，如果不存在null的话,直接进行数据的输出
        if (ObjectTool.isNull(headerAlias)) {
            directWriteSheet(data,sheet,obj->ObjectTool.isNull(obj)?"":StrTool.valueOf(obj));
        } else {
            // 判断headAlias不为空，那么就转换为set集合，后期循环使用
            Set<Map.Entry<String, String>> entries = headerAlias.entrySet();

            //判断要进行写额数据是否存在(不存在的不用写数据)
            if (CollTool.isNotEmpty(data)) {
                // 创建表中第一行（用来写表头）
                XSSFRow rowTitle = sheet.createRow(0);
                // 循环写数据
                for (int i = 0; i < data.size(); i++) {
                    Object obj = data.get(i);
                    XSSFRow row = sheet.createRow(i + 1);
                    int j = 0;
                    // 判断对象数据是否是map类型的
                    if (ObjectTool.isInstanceof(obj, Map.class)) {
                        Map<Object, Object> dataMap = (Map<Object, Object>) obj;
                        for (Map.Entry<String, String> entry : entries) {
                            Object o = dataMap.get(entry.getKey().trim());
                            // 判断是否是第一个对象数据（就是第一写数据），第一个不仅要进行写数据，还要写表头
                            if (i == 0) {
                                rowTitle.createCell(j).setCellValue(ObjectTool.isNull(entry.getValue()) ? "" : entry.getValue());
                            }
                            row.createCell(j).setCellValue(ObjectTool.isNull(o) ? "" : StrTool.valueOf(o));
                            j++;
                        }
                    } else {
                        for (Map.Entry<String, String> entry : entries) {
                            Object o = ReflectTool.getFieldValue(obj, entry.getKey().trim());
                            // 判断是否是第一个对象数据（就是第一写数据），第一个不仅要进行写数据，还要写表头
                            if (i == 0) {
                                rowTitle.createCell(j).setCellValue(ObjectTool.isNull(entry.getValue()) ? "" : entry.getValue());
                            }
                            row.createCell(j).setCellValue(ObjectTool.isNull(o) ? "" : StrTool.valueOf(o));
                            j++;
                        }
                    }
                    // 使用完成之后就释放掉这个对象
                    row = null;
                }
            } else {
                // 创建表中第一行（用来写表头）
                XSSFRow rowTitle = sheet.createRow(0);
                // 数据不存在，但是表头对象是存在的，那么就只写表头部分
                int j = 0;
                for (Map.Entry<String, String> entry : headerAlias.entrySet()) {
                    rowTitle.createCell(j).setCellValue(ObjectTool.isNull(entry.getValue()) ? "" : entry.getValue());
                    j++;
                }
            }
        }

        // 4. 导出工作簿
        try {
            // 数据导出
            workbook.write(os);
        } catch (IOException e) {
            throw new IORuntimeException(e.getMessage());
        }
    }

    /**
     * 写数据到指定的输出流
     *
     * @param exportConfiguration 导出配置对象
     * @author :loulan
     */
    public void write(ExcelExportConfiguration exportConfiguration) {
        AssertTool.notNull(exportConfiguration, "配置参数不能为空。");
        // 1. 先进行数据校验判断
        List<?> data = exportConfiguration.getData();
        List<ExcelHeaderConfiguration> headerConfig = exportConfiguration.getHeaderConfig();
        // 先判断excel里面是否能够放下这些数据
        isGtMaxrow(CollTool.isNotEmpty(exportConfiguration.getData()) ? data.size() : 0);
        // 判断最大列是否能够放下表头数据（表头显示数据）
        isGtMaxcol(CollTool.isNotEmpty(headerConfig) ? headerConfig.size() : 0);

        // 2. 创建工作表
        // 通过工作簿创建工作表(如果工作表的名称为空的，就创建一个空表)
        XSSFSheet sheet = StrTool.isBlank(exportConfiguration.getSheetName()) ? workbook.createSheet() : workbook.createSheet(exportConfiguration.getSheetName());


        // 3. 向工作表中写数据
        // 判断表头对应的别名是否存在，如果不存在null的话,直接进行数据的输出
        if (CollTool.isEmpty(headerConfig)) {
            directWriteSheet(data,sheet, exportConfiguration.getDataHandler());
        } else {
            int headerMaxRow = 0; // 获取表头最大行，万一没有配置数据开始行，就使用表头最大行+1作为数据开始行
            // 表头配置不为空，那么就先写表头
            for (ExcelHeaderConfiguration o : headerConfig) {
                // 获取表头中的最大行
                headerMaxRow = o.getPosition().getStartRow() > headerMaxRow ? o.getPosition().getStartRow() : headerMaxRow;
                headerMaxRow = o.getPosition().getEndRow() > headerMaxRow ? o.getPosition().getEndRow() : headerMaxRow;
                // 开始写表头
                XSSFRow row = sheet.getRow(o.getPosition().getStartRow());
                if (ObjectTool.isNull(row)) {
                    row = sheet.createRow(o.getPosition().getStartRow());
                }
                XSSFCell cell = row.createCell(o.getPosition().getStartCell());
                // 设置列的样式
                cell.setCellStyle(o.getCellStyle(workbook));
                // 设置当前列的显示内容
                cell.setCellValue(o.getName());
                // 如果没有必要进行单元格的合并就不进行合并操作
                if (o.getPosition().getStartRow() != o.getPosition().getEndRow() ||
                        o.getPosition().getStartCell() != o.getPosition().getEndCell()) {
                    sheet.addMergedRegion(new CellRangeAddress(o.getPosition().getStartRow(),
                            o.getPosition().getEndRow(),
                            o.getPosition().getStartCell(),
                            o.getPosition().getEndCell()));
                }
                // 如果列宽配置不为空那么就设置列宽
                if (ObjectTool.isNotNull(o.getWidth())) {
                    sheet.setColumnWidth(o.getPosition().getStartCell(), o.getWidth());
                }
            }

            // 数据不为空才能进行下一步的操作
            if (CollTool.isNotEmpty(data)) {
                // 获取要写数据的第一行位置
                int dataStartRow = ObjectTool.isNotNull(exportConfiguration.getDataStartRow()) ? exportConfiguration.getDataStartRow() : headerMaxRow + 1;
                // 循环写数据
                for (int i = 0; i < data.size(); i++) {
                    Object obj = data.get(i);
                    XSSFRow row = sheet.createRow(dataStartRow+i);

                    if (ObjectTool.isInstanceof(obj, Map.class)) {
                        Map<Object, Object> dataMap = (Map<Object, Object>) obj;
                        for (ExcelHeaderConfiguration header : headerConfig) {
                            Object value = dataMap.get(header.getKey());
                            row.createCell(header.getPosition().getStartCell())
                                    .setCellValue(ObjectTool.isNotNull(header.getDataHandler()) ?
                                            header.getDataHandler().apply(value) :
                                            exportConfiguration.getDataHandler().apply(value));
                        }
                    } else {
                        for (ExcelHeaderConfiguration header : headerConfig) {
                            Object value = ReflectTool.getFieldValue(obj, header.getKey());
                            row.createCell(header.getPosition().getStartCell()).setCellValue(ObjectTool.isNotNull(header.getDataHandler()) ?
                                    header.getDataHandler().apply(value) :
                                    exportConfiguration.getDataHandler().apply(value));
                        }
                    }
                    // 使用完成之后就释放掉这个对象
                    row = null;
                }
            } else {
                // 如果数据不存在，那么写完表头之后都没得写，直接输出一个空白的excel
            }
        }

        // 4. 导出工作簿
        try {
            // 数据导出
            workbook.write(exportConfiguration.getOutputStream());
        } catch (IOException e) {
            throw new IORuntimeException(e.getMessage());
        }
    }

    /**
     * 直接将数据写入sheet工作簿中的方法
     *
     * @param data        要写入的数据
     * @param sheet       工作簿
     * @param dataHandler 数据处理器
     * @author :loulan
     */
    private void directWriteSheet(List<?> data, XSSFSheet sheet, Function<Object, String> dataHandler) {
        // 如果表头不存在，那么就准备直接写数据
        if (CollTool.isNotEmpty(data)) {
            // 创建表中第一行（用来写表头）
            XSSFRow rowTitle = sheet.createRow(0);
            // 循环数据
            for (int i = 0; i < data.size(); i++) {
                Object obj = data.get(i);
                // 判断这个对象是否是map类型的
                if (ObjectTool.isInstanceof(obj, Map.class)) {
                    Map<Object, Object> dataMap = (Map<Object, Object>) obj;
                    // 判断最大列是否能够放下表头数据
                    isGtMaxcol(MapTool.isNotEmpty(dataMap) ? dataMap.size() : 0);
                    // 创建数据要写的行
                    XSSFRow row = sheet.createRow(i + 1);
                    int j = 0;
                    for (Map.Entry<Object, Object> entry : dataMap.entrySet()) {
                        // 判断是否是第一个数据，第一个不仅要进行写数据，还要写表头
                        if (i == 0) {
                            rowTitle.createCell(j).setCellValue(ObjectTool.isNull(entry.getKey()) ? "" : StrTool.valueOf(entry.getKey()));
                        }
                        row.createCell(j).setCellValue(dataHandler.apply(entry.getValue()));
                        j++;
                    }
                    row = null;
                } else {
                    // 获取所有的字段
                    Field[] fields = ReflectTool.getFields(obj.getClass());
                    // 判断最大列是否能够放下表头数据
                    isGtMaxcol(ArrayTool.isNotEmpty(fields) ? fields.length : 0);
                    // 创建数据要写的行
                    XSSFRow row = sheet.createRow(i + 1);
                    for (int j = 0; j < fields.length; j++) {
                        Field field = fields[j];
                        Object o = ReflectTool.getFieldValue(obj, field.getName());
                        // 判断是否是第一个数据，第一个不仅要进行写数据，还要写表头
                        if (i == 0) {
                            rowTitle.createCell(j).setCellValue(ObjectTool.isNull(field.getName()) ? "" : StrTool.valueOf(field.getName()));
                        }
                        row.createCell(j).setCellValue(dataHandler.apply(o));
                    }
                    // 使用完成之后就释放掉这个对象
                    row = null;
                }
            }
        } else {
            // 如果数据不存在，那么写表头都没得写，直接输出一个空白的excel
        }
    }

    /**
     * 关闭excel的工作簿
     *
     * @author :loulan
     */
    @Override
    public void close() {
        if (ObjectTool.isNotNull(workbook)) {
            try {
                workbook.close();
            } catch (IOException e) {
                throw new IORuntimeException(e.getMessage());
            }
        }
    }

    /**
     * 判断是否不大于最大行（如果大于了最大行数要抛出异常）
     *
     * @param size 数据的大小
     * @return 是否不大于最大行
     * @author :loulan
     */
    private Boolean isGtMaxrow(Integer size) {
        if (size <= MAX_ROW) {
            return Boolean.TRUE;
        } else {
            throw new ExcelException("The maximum number of rows in excel has been exceeded");
        }
    }

    /**
     * 判断是否不大于最大列（如果大于了最大列要抛出异常）
     *
     * @param size 数据的大小
     * @return 是否不大于最大列
     * @author :loulan
     */
    private Boolean isGtMaxcol(Integer size) {
        if (size <= MAX_COL) {
            return Boolean.TRUE;
        } else {
            throw new ExcelException("The maximum number of columns in excel has been exceeded");
        }
    }

    /**
     * 将构造方法私有化，不通过new方法创建对象
     *
     * @author :loulan
     */
    private ExcelXWriter() {
        this.workbook = new XSSFWorkbook();
    }

    /**
     * 将构造方法私有化，不通过new方法创建对象
     *
     * @param is 已经存在的工作簿
     * @author :loulan
     */
    private ExcelXWriter(InputStream is) throws IOException {
        this.workbook = new XSSFWorkbook(is);
    }

    /**
     * 创建当前类的对象
     *
     * @return {@link ExcelXWriter}对象
     * @author :loulan
     */
    public static ExcelXWriter create() {
        return new ExcelXWriter();
    }

    /**
     * 创建当前类的对象
     *
     * @param is      已经存在的excel表
     * @param isThrow 是否抛出异常，如果输入流异常是否抛出，true抛出，false不抛出也就是如果找不到就重新创建工作簿
     * @return {@link ExcelXWriter}对象
     * @author :loulan
     */
    public static ExcelXWriter create(InputStream is, boolean isThrow) {
        try {
            return new ExcelXWriter(is);
        } catch (IOException e) {
            if (isThrow) {
                throw new IORuntimeException(e);
            } else {
                return create();
            }
        }
    }
}
